import {
  Component,
  OnInit,
  OnChanges,
  ElementRef,
  TemplateRef,
  OnDestroy,
  Input,
  Output,
  EventEmitter,
  forwardRef,
  Renderer2,
  NgZone,
  ChangeDetectorRef,
  ViewChild,
  ChangeDetectionStrategy,
  SimpleChanges,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { InputBoolean, TypeUtils, LvConfig } from '../core/api';
import { TooltipDirective } from '../tooltip/api';
import { ThemeType } from '../core/tooltipbase/api';
import { LvSliderLines, LvSliderPoints, LvSliderDragType } from './interface';

@Component({
  selector: 'lv-slider',
  exportAs: 'lvSlider',
  templateUrl: './slider.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof SliderComponent => SliderComponent),
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.Default,
})
export class SliderComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
  @Input() lvDivision;
  @Input() lvStep: number;
  @Input() lvMin: number = 0;
  @Input() lvMax: number = 100;
  @Input() @InputBoolean() lvRange;
  @Input() lvLines: LvSliderLines[];
  @Input() lvPoints: LvSliderPoints[];
  @Input() @InputBoolean() lvDisabled;
  @Input() lvHandleColor: string | any[];
  @Input() @InputBoolean() lvReverse = false;
  @Input() lvDirection: string = 'horizontal';
  @Input() @InputBoolean() lvTrackVisible = true;
  @Input() @InputBoolean() lvHoverShowTooltip = false;
  // tslint:disable-next-line
  @Output() onChange: EventEmitter<any> = new EventEmitter();
  // tslint:disable-next-line
  @Output() onSlideEnd: EventEmitter<any> = new EventEmitter();
  @ViewChild('sliderTooltip', { static: false }) sliderTooltip;
  @ViewChild('sliderHandleEnd', { static: false }) sliderHandleEnd;

  @Input() lvTooltip: boolean | number | string | TemplateRef<void>;
  @ViewChild('sliderHandleStart', { static: false }) sliderHandleStart;
  @ViewChild(TooltipDirective, { static: false }) tooltip: TooltipDirective;
  @Input('lvTooltipTheme') lvTheme: ThemeType =
    LvConfig.sliderOptions.lvTheme || LvConfig.tooltipOptions.lvTheme || 'light'; // 主题颜色

  public initX: number;
  public initY: number;
  public sliderHoverData;
  public values: number[];
  public barWidth: number;
  public cells: any[] = [];
  public dragging: boolean;
  public dragListener: any;
  public barHeight: number;
  public SliderTooltipsValue;
  public mouseupListener: any;
  public tabindex: number = 0;
  public typeUtils = TypeUtils;
  public handleIndex: number = 0;
  public sliderHandleClick: boolean;
  public handleValues: number[] = [];
  public lvTooltipVisible: boolean = true;
  public sliderTooltipVisible: boolean = false;
  public lvTooltipVisibleDefault: boolean = false;
  public sliderDragType: LvSliderDragType = { left: false, right: false }; // 双滑块可拖拽的滑块
  constructor(
    public el: ElementRef,
    public renderer: Renderer2,
    private ngZone: NgZone,
    public cdr: ChangeDetectorRef,
  ) {}

  get handleColor(): string | any[] {
    if (TypeUtils.isArray(this.lvHandleColor)) {
      return this.getHandleColor(this.values[0]);
    } else {
      return this.lvHandleColor;
    }
  }

  get handleRightColor(): any {
    if (TypeUtils.isArray(this.lvHandleColor) && this.lvRange) {
      return this.getHandleColor(this.values[1]);
    } else {
      return this.lvHandleColor;
    }
  }

  public _onChange: any = (): void => {};

  registerOnTouched(): void {}

  ngOnDestroy(): void {
    this.unbindDragListeners();
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  // slider上移动鼠标
  moveSlider(event): void {
    this.showSliderTooltip(event);
  }

  // 鼠标进入slider
  enterSlider(event): void {
    this.showSliderTooltip(event);
  }

  constrain(val): number {
    return Math.max(Math.min(val, 100), 0);
  }

  hideToolTip(): void {
    if (this.tooltip) {
      this.toggleTooltip(false);
    }
  }

  /**
   * 从句柄中获取值
   * @param handleValue 句柄值
   */
  getValueFromHandle(handleValue: number): number {
    return (this.lvMax - this.lvMin) * (handleValue / 100) + this.lvMin;
  }

  unbindDragListeners(): void {
    if (this.dragListener) {
      this.dragListener();
    }
    if (this.mouseupListener) {
      this.mouseupListener();
    }
  }

  getDecimalsCount(value: number): number {
    if (value && Math.floor(value) !== value) {
      return value.toString().split('.')[1].length || 0;
    }
    return 0;
  }

  // tooltip切换
  toggleTooltip(show): void {
    if (!this.lvTooltipVisible) {
      return;
    }
    if (show) {
      this.tooltip.show();
    } else {
      this.tooltip.hide();
    }
  }

  ngOnInit(): void {
    this.calculateRangePosition(this.lvDivision);
    this.calculateMarkLine();
    this.calculateMarkPoint();
    this.lvTooltipVisible = this.lvTooltip !== false;
  }

  // 鼠标离开slider
  leaveSlider(): void {
    if (!this.lvHoverShowTooltip) {
      return;
    }
    this.sliderTooltipVisible = false;
    if (this.sliderTooltip) {
      this.sliderTooltip.hide();
    }
  }

  stepChange(newValue: number, oldValue: number): void {
    const val = oldValue + Math.round((newValue - oldValue) / this.lvStep) * this.lvStep;
    this.updateValue(val);
    this.updateHandleValue(val);
  }

  enterHandle(event, tooltip): void {
    if (this.sliderDragType.left || this.sliderDragType.right) {
      return;
    }
    if (tooltip) {
      this.tooltip = tooltip;
      this.toggleTooltip(true);
    }
  }

  leaveHandle(event, tooltip): void {
    if (this.sliderDragType.left || this.sliderDragType.right) {
      return;
    }
    if (tooltip) {
      this.tooltip = tooltip;
      this.toggleTooltip(false);
    }
  }

  getNormalizedValue(val: number): number {
    const decimalsCount = this.getDecimalsCount(this.lvStep);
    if (decimalsCount > 0) {
      return Number(val.toFixed(decimalsCount));
    } else {
      return Math.floor(val);
    }
  }

  selectMarkPoint(point: LvSliderPoints): boolean {
    if (this.lvRange) {
      return point.value >= this.values[0] && point.value <= this.values[1];
    } else {
      return point.value >= 0 && point.value <= this.values[0];
    }
  }

  spin(event, dir: number, handleIndex?: number): void {
    const step = (this.lvStep || 1) * dir;
    this.handleIndex = handleIndex;
    this.updateValue(this.values[this.handleIndex] + step);
    this.updateHandleValue();
    event.preventDefault();
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvDivision) {
      this.calculateRangePosition(this.lvDivision);
    }
    if (changes.lvLines) {
      this.calculateMarkLine();
    }
    if (changes.lvPoints) {
      this.calculateMarkPoint();
    }
  }

  showSliderTooltip(event): void {
    if (!this.lvHoverShowTooltip) {
      return;
    }
    this.updateElementData();
    this.sliderHoverData = this.handleSliderTooltips(event);
    this.sliderTooltipVisible = true;
    if (this.sliderTooltip) {
      this.sliderTooltip.show();
      this.sliderTooltip.updatePosition();
    }
  }

  // 设置滑块颜色
  getHandleColor(value): any {
    let handleColor;
    const colorArray = Array.from(this.lvHandleColor);
    for (let i = colorArray.length - 1; i >= 0; i--) {
      if (value >= colorArray[i][0]) {
        handleColor = colorArray[i][1];
        break;
      } else {
        handleColor = null;
      }
    }
    return handleColor;
  }

  onHandleKeydown(event, handleIndex?: number): void {
    if (event.which === 38 || event.which === 39) {
      this.lvReverse ? this.spin(event, -1, handleIndex) : this.spin(event, 1, handleIndex);
    } else if (event.which === 37 || event.which === 40) {
      this.lvReverse ? this.spin(event, 1, handleIndex) : this.spin(event, -1, handleIndex);
    }
  }

  onMouseDown(event, index?: number): void {
    event.preventDefault();

    if (this.lvDisabled) {
      return;
    }

    if (!this.lvRange) {
      this.dragging = true;
    } else {
      this.sliderDragType = {
        left: index === 0,
        right: index === 1,
      };
    }

    this.updateElementData();
    this.sliderHandleClick = true;
    this.handleIndex = index;
    this.bindDragListeners();
    event.currentTarget.focus();
  }

  updateElementData(): void {
    const rect = this.el.nativeElement.children[0].getBoundingClientRect();
    const doc = document.documentElement;
    this.initX = rect.left + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
    this.initY = rect.top + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
    this.barWidth = this.el.nativeElement.children[0].offsetWidth;
    this.barHeight = this.el.nativeElement.children[0].offsetHeight;
  }

  onBarClick(event): void {
    if (this.lvDisabled) {
      return;
    }
    if (!this.sliderHandleClick) {
      this.updateElementData();
      this.handleChange(event);
      if (this.lvRange) {
        this.onSlideEnd.emit({
          originalEvent: event,
          values: this.values,
        });
      } else {
        this.onSlideEnd.emit({
          originalEvent: event,
          value: this.values[0],
        });
      }
    }
    this.sliderHandleClick = false;
  }

  setValueFromHandle(event: Event, handleValue: any): void {
    const newValue = this.getValueFromHandle(handleValue);

    if (this.lvRange) {
      if (this.lvStep) {
        this.stepChange(newValue, this.values[this.handleIndex]);
      } else {
        this.updateValue(newValue, event);
      }
    } else if (this.lvStep) {
      this.stepChange(newValue, this.values[0]);
    } else {
      this.handleValues = [handleValue];
      this.updateValue(newValue, event);
    }
  }

  handleChange(event): void {
    let handleValue;
    if (this.lvDirection !== 'vertical') {
      handleValue = this.lvReverse
        ? Math.floor(((this.initX + this.barWidth - event.pageX) * 100) / this.barWidth)
        : Math.floor(((event.pageX - this.initX) * 100) / this.barWidth);
    } else {
      handleValue = this.lvReverse
        ? Math.floor(((event.pageY - this.initY) * 100) / this.barHeight)
        : Math.floor(((this.initY + this.barHeight - event.pageY) * 100) / this.barHeight);
    }
    this.setValueFromHandle(event, handleValue);
  }

  onPointClick(event, point): void {
    event.stopPropagation();
    if (this.lvDisabled) {
      return;
    }
    if (!this.sliderHandleClick) {
      this.updateElementData();
      this.setValueFromHandle(event, ((point.value - this.lvMin) / (this.lvMax - this.lvMin)) * 100);
      if (this.lvRange) {
        this.onSlideEnd.emit({
          originalEvent: event,
          values: this.values,
        });
      } else {
        this.onSlideEnd.emit({
          originalEvent: event,
          value: this.values[0],
        });
      }
    }
    this.sliderHandleClick = false;
  }

  // 标记区间点渲染
  calculateMarkPoint(): void {
    if (!this.lvPoints) {
      return;
    }
    let style;
    this.lvPoints.forEach((point): void => {
      if (!point.pointStyle) {
        point.pointStyle = [];
      }
      if (!point.textStyle) {
        point.textStyle = [];
      }
      const pointVal = ((point.value - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      if (this.lvDirection !== 'vertical') {
        style = {
          left: `${pointVal}%`,
        };
      } else {
        style = {
          bottom: `${pointVal}%`,
        };
      }
      point.pointStyle = { ...point.pointStyle, ...style };
      point.textStyle = { ...point.textStyle, ...style };
    });
  }

  // 标记区间段渲染
  calculateMarkLine(): void {
    if (!this.lvLines) {
      return;
    }
    let style;
    this.lvLines.forEach((line): void => {
      if (!line.style) {
        line.style = [];
      }
      const lines = [];
      lines[0] = ((line.value[0] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      lines[1] = ((line.value[1] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      if (this.lvDirection !== 'vertical') {
        style = {
          left: `${lines[0]}%`,
          width: `${lines[1] - lines[0]}%`,
        };
      } else {
        style = {
          bottom: `${lines[0]}%`,
          height: `${lines[1] - lines[0]}%`,
        };
      }
      line.style = { ...line.style, ...style };
    });
  }

  writeValue(value: any): void {
    if (this.lvRange) {
      // 双滑块
      if (value && !(value instanceof Array)) {
        // eslint-disable-next-line no-console
        console.error('The double-slider pattern requires binding of the type sliderArray.Please refer to the API');
      }
      this.values = value && value instanceof Array ? value : [0, 0];
      this.handleValues[0] = ((this.values[0] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      this.handleValues[1] = ((this.values[1] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
    } else {
      // 单滑块
      this.values = [value || 0];
      this.handleValues = [((this.values[0] - this.lvMin) / (this.lvMax - this.lvMin)) * 100];
    }
    this.updateHandleValue();
    this.cdr.markForCheck();
  }

  // 单元point渲染
  calculateRangePosition(lvDivision): void {
    if (!lvDivision || lvDivision < 1) {
      return;
    }
    this.cells = Array.from({ length: lvDivision + 1 }).map(
      (
        item,
        key,
      ):
        | { index: number; style: { left: string } }
        | {
            index: number;
            style: { bottom: string };
          } => {
        if (this.lvDirection !== 'vertical') {
          return {
            index: key,
            style: {
              left: `${(1 / lvDivision) * 100 * key}%`,
            },
          };
        } else {
          return {
            index: key,
            style: {
              bottom: `${(1 / lvDivision) * 100 * key}%`,
            },
          };
        }
      },
    );
  }

  handleSliderTooltips(event): number {
    let handleValue;
    if (this.lvDirection !== 'vertical') {
      handleValue = this.lvReverse
        ? ((this.initX + this.barWidth - event.pageX) * 100) / this.barWidth
        : ((event.pageX - this.initX) * 100) / this.barWidth;
    } else {
      handleValue = this.lvReverse
        ? ((event.pageY - this.initY) * 100) / this.barHeight
        : ((this.initY + this.barHeight - event.pageY) * 100) / this.barHeight;
    }
    handleValue = this.getValueFromHandle(handleValue);
    if (handleValue < this.lvMin) {
      handleValue = this.lvMin;
    } else if (handleValue > this.lvMax) {
      handleValue = this.lvMax;
    }
    this.SliderTooltipsValue = ((handleValue - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
    return this.getNormalizedValue(handleValue);
  }

  // 双向滑块点击时值变更逻辑
  updateValueWhenClick(val: number): void {
    if (val < this.values[1]) {
      if (val < this.lvMin) {
        this.values[0] = this.getNormalizedValue(this.lvMin);
        this.handleValues[0] = 0;
      } else {
        this.values[0] = this.getNormalizedValue(val);
        this.handleValues[0] = ((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      }
      this.tooltip = this.sliderHandleStart.tooltip;
      this.sliderHandleStart.elementRef.nativeElement.focus();
    } else {
      if (val > this.lvMax) {
        this.values[1] = this.getNormalizedValue(this.lvMax);
        this.handleValues[1] = 100;
      } else {
        this.values[1] = this.getNormalizedValue(val);
        this.handleValues[1] = ((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      }
      this.tooltip = this.sliderHandleEnd.tooltip;
      this.sliderHandleEnd.elementRef.nativeElement.focus();
    }
  }

  // 实际值
  updateValue(valParam: number, event?: Event): void {
    let val = valParam;
    if (this.lvRange) {
      // 双滑块
      this.updateValueWhenDragging(val);
      if (!this.sliderDragType.left && !this.sliderDragType.right) {
        this.updateValueWhenClick(val);
      }
      this.values = this.values.slice();
      this._onChange(this.values);
      this.onChange.emit({ event, value: this.values });
    } else {
      // 单滑块
      if (val < this.lvMin) {
        val = this.lvMin;
        this.handleValues = [0];
      } else if (val > this.lvMax) {
        val = this.lvMax;
        this.handleValues = [100];
      }
      this.values = [this.getNormalizedValue(val)];
      this._onChange(this.values[0]);
      this.onChange.emit({ event, value: this.values[0] });
      this.sliderHandleStart.elementRef.nativeElement.focus();
    }

    if (this.tooltip) {
      this.toggleTooltip(true);
      this.tooltip.updatePosition();
    }
  }

  // handle显示位置的百分比
  updateHandleValue(valParam?): void {
    let val = valParam;
    const vals = [];
    if (this.lvRange) {
      if (TypeUtils.isNotNil(val)) {
        const index = this.handleIndex;
        this.handleValues[index] = this.constrain(((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100);
      } else {
        vals[0] = this.values[0] < this.lvMin ? this.lvMin : this.values[0];
        vals[1] = this.values[1] > this.lvMax ? this.lvMax : this.values[1];
        const _startHandleValue = ((vals[0] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
        const _endHandleValue = ((vals[1] - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
        if (_startHandleValue > _endHandleValue) {
          this.handleValues = [_endHandleValue, _startHandleValue];
        } else {
          this.handleValues = [_startHandleValue, _endHandleValue];
        }
      }
    } else {
      val = TypeUtils.isNotNil(val) ? val : this.values[0];
      this.handleValues = [this.constrain(((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100)];
    }
  }

  bindDragListeners(): void {
    this.ngZone.runOutsideAngular((): void => {
      if (!this.dragListener) {
        this.handleWhenNotDragListener();
      }
      if (!this.mouseupListener) {
        this.handleWhenNotMouseupListener();
      }
    });
  }

  handleWhenNotDragListener(): void {
    this.dragListener = this.renderer.listen('document', 'mousemove', (event): void => {
      if (this.dragging || this.sliderDragType.left || this.sliderDragType.right) {
        this.ngZone.run((): void => {
          this.handleChange(event);
        });
      }
    });
  }

  handleWhenNotMouseupListener(): void {
    this.mouseupListener = this.renderer.listen('document', 'mouseup', (event): void => {
      let flag = this.dragging || this.sliderDragType.left || this.sliderDragType.right;
      if (!flag) {
        return;
      }
      this.dragging = false;
      this.sliderDragType = {
        left: false,
        right: false,
      };
      this.ngZone.run((): void => {
        if (this.lvRange) {
          this.onSlideEnd.emit({
            originalEvent: event,
            values: this.values,
          });
        } else {
          this.onSlideEnd.emit({
            originalEvent: event,
            value: this.values[0],
          });
        }
      });
      if (this.tooltip) {
        this.toggleTooltip(false);
      }
    });
  }

  // 双向滑块拖拽时值变更逻辑
  updateValueWhenDragging(val: number): void {
    const toolTipHandle = (left = true, exchange = false): void => {
      const order = (Number(left) + Number(exchange)) % 2;
      const element = order ? this.sliderHandleStart : this.sliderHandleEnd;
      if (exchange) {
        this.tooltip.hide();
      }
      this.tooltip = element.tooltip;
      element.elementRef.nativeElement.focus();
    };
    let direction = true;
    let flag = false;
    if (this.sliderDragType.left) {
      if (val < this.lvMin) {
        this.values[0] = this.getNormalizedValue(this.lvMin);
        this.handleValues[0] = 0;
      } else if (val <= this.values[1]) {
        this.values[0] = this.getNormalizedValue(val);
        this.handleValues[0] = ((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      } else {
        this.values[0] = this.values[1];
        this.handleValues[0] = this.handleValues[1];
        this.sliderDragType = {
          left: false,
          right: true,
        };
        this.handleIndex = 1;
        flag = true;
      }
    } else if (this.sliderDragType.right) {
      direction = false;
      if (val > this.lvMax) {
        this.values[1] = this.getNormalizedValue(this.lvMax);
        this.handleValues[1] = 100;
      } else if (val >= this.values[0]) {
        this.values[1] = this.getNormalizedValue(val);
        this.handleValues[1] = ((val - this.lvMin) / (this.lvMax - this.lvMin)) * 100;
      } else {
        this.values[1] = this.values[0];
        this.handleValues[1] = this.handleValues[0];
        this.sliderDragType = {
          left: true,
          right: false,
        };
        this.handleIndex = 0;
        flag = true;
      }
    }
    toolTipHandle(direction, flag);
  }
}
